package org.pine.common.utils;

import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import org.pine.api.enums.ResultCodeEnum;
import org.pine.common.components.SpringBeanUtil;
import org.pine.common.exception.PineException;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Supplier;

/**
 * redis锁
 *
 * @author pine manage
 * @since 2024-08-09
 */
public class RedisLock {

    private static LockTemplate lockTemplate;

    /**
     * 获取锁超时时间
     */
    private static final Long LIMIT_STREAM_ACQUIRE_TIMEOUT = 0L;
    private static final Long ACQUIRE_TIMEOUT = 3000L;

    /**
     * 锁过期时间
     */
    private static final Long EXPIRE = 5000L;

    /**
     * redis分布式锁（redisson自动续锁）
     *
     * @param bizRunnable 业务逻辑
     * @param key         锁key
     */
    public static void lock(Runnable bizRunnable, String key, Long acquireTimeout) {
        initLockTemplate();
        // 获取锁超时时间,默认3秒
        acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);
        // expire锁过期时间为-1：不过期，redisson 自动续锁
        LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);
        if (lockInfo == null) {
            throw new PineException(ResultCodeEnum.DATA_EXCEPTION.getCode(), "获取锁失败");
        }

        try {
            bizRunnable.run();
            lockTemplate.releaseLock(lockInfo);
        } catch (Exception e) {
            lockTemplate.releaseLock(lockInfo);
            throw e;
        }
    }

    /**
     * redis分布式锁（redisson自动续锁）
     *
     * @param bizCallable 业务逻辑
     * @param key         锁key
     */
    public static <Result> Result lock(Callable<Result> bizCallable, String key, Long acquireTimeout) throws Exception {
        initLockTemplate();
        // 获取锁超时时间,默认3秒
        acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);
        // expire锁过期时间为-1：不过期，redisson 自动续锁
        LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);
        if (lockInfo == null) {
            throw new PineException(ResultCodeEnum.DATA_EXCEPTION.getCode(), "获取锁失败");
        }

        try {
            Result result = bizCallable.call();
            lockTemplate.releaseLock(lockInfo);
            return result;
        } catch (Exception e) {
            lockTemplate.releaseLock(lockInfo);
            throw e;
        }
    }

    /**
     * 加锁限流<br>
     * <b>此处注意：上锁后不手动解锁，等待过期时间自动解锁</b>
     *
     * @param expire         锁过期时间
     * @param acquireTimeout 获取锁超时时间
     * @param keySupplier    计算锁key的函数
     */
    public static void lockAndLimitStream(Object param, Long expire, Long acquireTimeout, Supplier<String> keySupplier) {
        initLockTemplate();
        // 锁过期时间默认5秒
        expire = Optional.ofNullable(expire).orElse(EXPIRE);
        // 获取锁超时时间默认0秒
        acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(LIMIT_STREAM_ACQUIRE_TIMEOUT);

        // 计算锁key
        String key = keySupplier.get();
        // 尝试获取锁
        LockInfo lockInfo = lockTemplate.lock(key, expire, acquireTimeout);

        // 获取锁失败
        if (lockInfo == null) {
            throw new PineException(ResultCodeEnum.FREQUENT_ACCESS_EXCEPTION.getCode(), "调用过于频繁，业务处理中，请稍后再试");
        }
    }


    /**
     * 初始化lockTemplate
     */
    private static void initLockTemplate() {
        if (lockTemplate == null) {
            lockTemplate = SpringBeanUtil.getByClass(LockTemplate.class);
        }
        Objects.requireNonNull(lockTemplate, "lockTemplate is null");
    }
}
